<!--
 @description  可单选与多选
 @fileName index.vue
 @author zengqiongying
 @created 2022/11/16 16:23:01
-->
<template>
  <div class="label-select-wrap">
    <div
      v-for="(item,i) in optionList"
      :key="`${item[valueName]}${i}${timer}`"
      :class="[
        'item',
        {
          'selected':(![undefined,null].includes(item[valueName])) && ( multiple?curSel.includes(item[valueName].toString()):curSel.toString()===item[valueName].toString())
        }
      ]"
      :style="itemStyle"
      @click="handleClickLabel(item)"
    >
      <div class="i-wrap">
        <span class="text">{{ labelSet(item) }}</span>
      </div>
    </div>
  </div>
</template>

<script>
import request from '@/utils/request'
import { isDef } from '@/utils'
const none = [undefined, null, '']
export default {
  name: 'LabelSelect',
  props: {
    disabled: {
      type: Boolean,
      default: false
    },
    column: {
      type: Number,
      default: 3
    },
    value: {
      type: [String, Number, Array],
      default: ''
    }, // 选中的值
    label: {
      type: [String, Array],
      default: undefined
    }, // 选中的值的名称
    optionData: {
      // 选项列表数据，该数据不为空时，不请求远程搜索
      type: Array,
      default: null
    },
    valueName: {
      // 选项值键名
      type: String,
      default: 'id'
    },
    labelName: {
      // 选项名称键名
      type: String,
      default: ''
    },
    labelFunc: {
      // 选项名称自定义回调方法
      type: Function,
      default: null
    },
    currentId: {
      // 选中值的Id
      type: String,
      default: ''
    },
    curOptions: {
      // 当前选中选项，默认为对象，multiple为true时为数组
      type: [Array, Object],
      default: () => {
        return {}
      }
    },
    customName: {
      // 输入值的查询键名，有值时使用，否则使用labelName
      type: String,
      default: ''
    },
    customOperator: {
      // 输入值的查询键名匹配符，有值时使用，否则使用labelName
      type: String,
      default: 'like' // like 、 =
    },
    url: {
      // 查询地址
      type: String,
      default: ''
    },
    method: {
      // 请求方式 get/post,默认post
      type: String,
      default: 'post'
    },
    params: {
      // 请求参数,query为具体参数，type为传参方式（1 body方式,0 formData标准分页,2 formData方式）
      type: Object,
      default: () => {
        return { query: null, type: 0 }
      }
    },
    paramsRaw: {
      // 标准分页查询额外参数(对应分页查询入参：paramsRaw)
      type: Object,
      default: () => ({})
    },
    parentId: {
      // 父级参数(父级Id或code)，改变时刷新列表
      type: [String, Number, Array],
      default: ''
    },
    mustPid: {
      // 是否必须有父级id
      type: Boolean,
      default: false
    },
    mustVal: {
      // 是否必须有值查询
      type: Boolean,
      default: false
    },
    reqHeaders: {
      // 请求头参数
      type: Object,
      default: null
    },
    includes: {
      // 包含指定的字典编码，若为Function则返回值需要是字典编码数组
      type: [Array, Function],
      default: undefined
    },
    excludes: {
      // 排除指定的字典编码，若为Function则返回值需要是字典编码数组
      type: [Array, Function],
      default: undefined
    },
    multiple: {
      // 是否多选
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      curSel: '',
      timer: new Date().getTime(),
      templabel: null, // 选中项的name值，没有绑定label时使用
      list: [],
      exCodeList: [],
      inCodeList: []
    }
  },

  computed: {
    itemStyle() {
      const style = {}
      if (this.column && this.column > 1) {
        const w = Math.floor((100 / this.column) * 100) / 100
        style.width = `${w}%`
      }
      return style
    },
    optionList() {
      let t = this.list
      if (this.inCodeList instanceof Array && this.inCodeList.length > 0) {
        t = t.filter(e => this.inCodeList.some(v => isDef(e[this.valueName]) && v.toString() === e[this.valueName].toString()))
      }
      if (this.exCodeList instanceof Array && this.exCodeList.length > 0) {
        t = t.filter(e => !this.exCodeList.some(v => isDef(e[this.valueName]) && v.toString() === e[this.valueName].toString()))
      }
      return t
    }
  },
  watch: {
    value: {
      handler(val) {
        if (this.multiple) {
          this.curSel = []
          if (val && val instanceof Array) {
            val.forEach(e => {
              if (!none.includes(e)) {
                this.curSel.push(e.toString())
              }
            })
          }
        } else {
          this.curSel = none.includes(val) ? '' : val.toString()
          if (!val) {
            this.templabel = null
          }
        }
        // 数据请求未返回时，设置默认选项
        this.setList(this.curSel)
      },
      deep: true,
      immediate: true
    },
    url: {
      handler() {
        this.remoteMethod()
      },
      immediate: true
    },
    parentId: {
      // 监听parentId实现父级变化后刷新列表
      handler(val) {
        this.remoteMethod()
      }
    },
    optionData: {
      handler(val) {
        this.remoteMethod()
      },
      immediate: true
    },
    includes: {
      handler(v) {
        this.getCludeList()
      },
      deep: true
    },
    excludes: {
      handler(v) {
        this.getCludeList()
      },
      deep: true
    }
  },
  created() {
    this.getCludeList()
  },

  methods: {
    checkSelected(val) {
      if (this.multiple) {
        const isArr = this.curSel instanceof Array
        if (!isArr) {
          return false
        }
        return this.curSel.includes(val.toString())
      }
      return this.curSel === val.toString()
    },
    handleClickLabel(data) {
      if (!isDef(data[this.valueName])) return
      let selectedValue = this.curSel
      const val = data[this.valueName].toString()
      if (this.multiple) {
        if (selectedValue instanceof Array) {
          const index = selectedValue.findIndex(v => v.toString() === val)
          if (index === -1) {
            selectedValue.push(val)
          } else {
            selectedValue.splice(index, 1)
          }
        } else {
          selectedValue = [val]
        }
      } else {
        if (selectedValue === val) {
          selectedValue = ''
        } else {
          selectedValue = val
        }
      }
      this.handleChange(selectedValue)
    },
    handleChange(val) {
      if (this.multiple) {
        const nameList = []
        const ary = []
        const ids = []
        if (val && val.length > 0) {
          val.forEach(code => {
            const item = this.optionList.find(e => {
              return e[this.valueName].toString() === code.toString()
            })
            nameList.push(item[this.labelName])
            ary.push(item)
            if (item.id) {
              ids.push(item.id)
            }
          })
        }
        this.$emit('update:label', nameList)
        this.$emit('update:curOptions', ary)
        this.$emit('update:currentId', ids)
        this.$emit('change', ary)
      } else {
        const obj = !none.includes(val) ? this.optionList.find(e => {
          return e[this.valueName].toString() === val.toString()
        }) : ''
        const name = obj ? obj[this.labelName] : ''
        this.templabel = name
        this.$emit('update:label', name)
        this.$emit('update:curOptions', obj)
        this.$emit('update:currentId', obj ? obj.id : '')
        this.$emit('change', obj)
      }
      this.$emit('update:value', val)
      this.$emit('input', val)
    },
    setList(val) {
      this.timer = new Date().getTime()
      // 判断列表中是否有传入的选项
      if (this.multiple) {
        for (const i in val) {
          const bol = this.list.every(e => {
            return isDef(e[this.valueName]) && e[this.valueName].toString() !== val[i].toString()
          })
          // 没有当前选项则加上
          if (bol) {
            if (this.label) {
              const d = {
                [this.labelName]: this.label[i] || '',
                [this.valueName]: val[i]
              }
              this.list.unshift(d)
            } else {
              const d = {
                [this.labelName]: this.templabel ? this.templabel[i] || '' : '',
                [this.valueName]: val[i]
              }
              this.list.unshift(d)
            }
          }
        }
      } else {
        const bol = this.list.every(e => {
          return isDef(e[this.valueName]) && e[this.valueName].toString() !== val.toString()
        })
        // 没有当前选项则加上
        if (bol && this.value) {
          const d = {
            [this.labelName]: this.label ? this.label : this.templabel || '',
            [this.valueName]: val
          }
          this.list.unshift(d)
        }
      }
    },
    remoteMethod(val) {
      if (this.optionData) {
        this.list = this.optionData
        this.setList(this.curSel)
        return
      }
      if (this.mustPid && !this.parentId) {
        this.list = []
        this.setList(this.curSel)
        return
      }
      if (this.mustVal && !val) {
        return
      }
      const formObj = new FormData()
      const filtersRaw = []
      const obj = { ...this.params.query }
      if (val) delete obj[this.customName]
      if (obj) {
        for (const i in obj) {
          if (Array.isArray(obj[i]) && obj[i].length === 2) {
            filtersRaw.push({
              id: Math.floor(Math.random() * 0xffffffffff).toString(16),
              value: obj[i][0],
              property: i,
              operator: '>='
            })
            filtersRaw.push({
              id: Math.floor(Math.random() * 0xffffffffff).toString(16),
              value: obj[i][1],
              property: i,
              operator: '<='
            })
          } else if (obj.hasOwnProperty(i) && Object.prototype.toString.call(obj[i]) === '[object Object]') {
            const v = obj[i]
            if (v.value instanceof Array) {
              if (v.value.length <= 0) {
                continue
              } else {
                v.value = `[${v.value.join(',')}]`
              }
            }
            filtersRaw.push({
              id: Math.floor(Math.random() * 0xffffffffff).toString(16),
              value: v.value,
              property: i,
              operator: v.operator
            })
          } else if (obj[i] !== '[]' && !none.includes(obj[i])) {
            filtersRaw.push({
              id: Math.floor(Math.random() * 0xffffffffff).toString(16),
              value: obj[i],
              property: i,
              operator: 'like'
            })
          }
        }
      }
      if (val) {
        filtersRaw.push({
          id: Math.floor(Math.random() * 0xffffffffff).toString(16),
          value: val,
          property: this.customName ? this.customName : this.labelName,
          operator: this.customName ? this.customOperator : 'like'
        })
      }
      const paramsRaw = {}
      for (const [k, v] of Object.entries(this.paramsRaw)) {
        if (none.includes(v)) {
          continue
        }
        if (v instanceof Array) {
          const _v = v.filter(e => !none.includes(e))
          if (_v.length > 0) {
            // paramsRaw[k] = `[${_v.join(',')}]`
            paramsRaw[k] = _v
          }
        } else if (Object.prototype.toString.call(v) === '[object Object]') {
          paramsRaw[k] = JSON.stringify(v)
        }
      }
      formObj.append('page', 1)
      formObj.append('rows', 50)
      formObj.append('filtersRaw', JSON.stringify(filtersRaw))
      formObj.append('paramsRaw', JSON.stringify(paramsRaw))
      // const obj = this.params.query
      const strList = ['page=1', 'rows=50']
      let str = ''
      for (const i in obj) {
        if (!none.includes(obj[i])) {
          strList.push(i + '=' + obj[i])
        }
      }
      if (val) {
        strList.push(
          (this.customName ? this.customName : this.labelName) + '=' + val
        )
      }
      if (strList.length) {
        str = '?' + strList.join('&')
      }

      this.loading = true
      let reqConfig = {}
      if (this.method.toLowerCase() === 'get') {
        reqConfig = {
          url: this.url + str,
          method: this.method
        }
      } else {
        let reqData = ''
        if (this.params.type === 0) {
          // 标准filter查询
          reqData = formObj
        } else if (this.params.type === 3) {
          // JSON数据
          reqData = { ...obj, ...{ page: 1, rows: 50 }}
        } else if (this.params.type === 2) {
          // 普通formData
          reqData = new FormData()
          for (const key in obj) {
            reqData.append(key, obj[key])
          }
          reqData.append('page', 1)
          reqData.append('rows', 50)
        }
        reqConfig = {
          url: this.url + (this.params.type === 1 ? str : ''),
          method: this.method,
          data: reqData
        }
      }
      if (this.reqHeaders && Object.keys(this.reqHeaders).length > 0) {
        reqConfig.headers = this.reqHeaders
      }

      this.getCludeList()
      request(reqConfig).then(res => {
        this.loading = false
        if (res.success === 1) {
          if (Object.keys(res.data).indexOf('records') === -1) {
            this.list = res.data.map(e => {
              e[this.valueName] = e[this.valueName].toString()
              return e
            })
          } else {
            this.list = res.data.records.map(e => {
              e[this.valueName] = e[this.valueName].toString()
              return e
            })
          }
          this.$emit('data-change', this.list)
          this.setList(this.curSel)
        }
      }).catch(er => {
        this.loading = false
      })
    },
    labelSet(item) {
      if (this.labelFunc) {
        return this.labelFunc(item)
      } else {
        return item[this.labelName]
      }
    },
    getCludeList() {
      let inCodeList = []
      if (this.includes) {
        if (this.includes instanceof Array) {
          inCodeList = this.includes
        } else if (this.includes instanceof Function) {
          try {
            const result = this.includes()
            if (result instanceof Promise) {
              result.then((data) => {
                inCodeList = data instanceof Array ? data : []
              })
            } else {
              inCodeList = result instanceof Array ? result : []
            }
          } catch (e) {
            this.inCodeList = []
          }
        }
      }
      this.inCodeList = inCodeList

      let exCodeList = []
      if (this.excludes) {
        if (this.excludes instanceof Array) {
          exCodeList = this.excludes || []
        } else if (this.excludes instanceof Function) {
          try {
            const result = this.excludes()
            if (result instanceof Promise) {
              result.then((data) => {
                exCodeList = data instanceof Array ? data : []
              })
            } else {
              exCodeList = result instanceof Array ? result : []
            }
          } catch (e) {
            exCodeList = []
          }
        }
      }
      this.exCodeList = exCodeList
    }
  }
}
</script>
<style lang='less' scoped>
.label-select-wrap{
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  .item{
    padding:8px;
    height: 52px;
    line-height: 36px;
    box-sizing: border-box;
    .i-wrap{
      position: relative;
      height: 100%;
      width: 100%;
      text-align: center;
      background: rgba(241, 241, 242, 0.5);
      border-radius: 6px;
      box-sizing: border-box;
      overflow: hidden;
      .text{
        display: block;
        padding: 0 10px;
        font-size: 12px;
        color: #3C4458;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }
    &.selected{
      .i-wrap{
        background:rgba(22, 188, 131, 0.1);
        .text{
          color: #16BC83;
          font-weight: bold;
        }
      }
    }
  }
}
</style>
